import fetch from "node-fetch";
import {
  BackendServices,
  StringIndexer,
  StringIndexerTo,
  isUndefined,
  toString,
  reduceToObject,
  toFixed,
  _min,
  union,
  deepClone,
} from "mmb";
import { getApiAddress } from "../store/helper";
import { ApiInvokerFilter, ApiInvokerMap, ApiInvokerBuilder, defaults} from "./ApiInovkerBuilder"
import {
  CreateProductParams,
  UpdateProductParams,
  ProductAttributeDefExt,
  ProductSpec,
  User,
} from "../service/declareTypes";
import { stringify } from "event-stream";
import DataSource from "../bg-enums/dataSource";
import { CredibleLoginParams, credibleLogin } from "./accountProxy";

const getBaseUrl = () => {
  return getApiAddress(BackendServices.WEBPOS)();
};

const getHeaders = (newHeaders: StringIndexerTo<string>) => {
  const headers: StringIndexerTo<string> = {
    "Content-Type": "application/json",
    ...newHeaders,
  };
  return headers;
};


export class UniqueTokenExpiredError extends Error {
  // __proto__: Error;
  constructor(message?: string) {
    const trueProto = new.target.prototype;
    super(message);
    // Alternatively use Object.setPrototypeOf if you have an ES6 environment.
    // this.__proto__ = trueProto;
    Object.setPrototypeOf(this, trueProto)
  }
  compensate(p: CredibleLoginParams) {
    return credibleLogin(p);
  }
}

export const getWebposBrandList = (uniqueToken: string) => {
  const url = `${getBaseUrl()}/web/v3/brand/list`;
  // console.log('uniqueToken=>', uniqueToken)
  const headers = getHeaders({ auth: uniqueToken });
  const b: ApiInvokerBuilder = Object.assign({}, defaults, {
    exec: () =>
      fetch(url, { method: "GET", headers: headers })
        .then((res) => res.json())
        .then((ret) => {
          if (
            !isUndefined(ret.error_code) &&
            ret.error_code === "SSO_TOKEN_NOT_FOUND"
          ) {
            console.log("uniqueToken expired 需要刷新");
            throw new UniqueTokenExpiredError("SSO_TOKEN_NOT_FOUND");
          }
          return ret;
        })
        .then((ret) => {
          // console.log("getWebposBrandList ret=>", ret);
          if (!isUndefined(ret.error_message)) {
            throw Error(ret.error_message);
          }
          return ret;
        })
        .then((ret) =>
          ret.sg_list.map((row) => ({
            remotePK: toString(row.sg_id),
            pic: "",
            name: row.sg_name,
          }))
        ),
  });
  return b;
};

export const createWebposBrand = (uniqueToken: string) => {
  const url = `${getBaseUrl()}/web/v3/brand/create`;
  const headers = getHeaders({ auth: uniqueToken });
  const b: ApiInvokerBuilder = Object.assign({}, defaults, {
    setBody: (v: StringIndexer) => {
      b.data = v;
      return b;
    },
    exec: () =>
      fetch(url, {
        method: "POST",
        headers: headers,
        body: isUndefined(b.data) ? undefined : JSON.stringify(b.data),
      })
        .then((res) => res.json())
        .then((ret) => {
          if (
            !isUndefined(ret.error_code) &&
            ret.error_code === "SSO_TOKEN_NOT_FOUND"
          ) {
            console.log("uniqueToken expired 需要刷新");
            throw new UniqueTokenExpiredError("SSO_TOKEN_NOT_FOUND");
          }
          return ret;
        })
        .then((ret) => {
          console.log("createWebposBrand ret=>", ret);
          if (!isUndefined(ret.error_message)) {
            throw Error(ret.error_message);
          }
          return ret;
        })
        .then((ret) => ({
          remotePK: toString(ret.sg_id),
        })),
  });

  return b;
};

export const genWebToken = (
  uniqueToken: string,
  reinvokeOnErr: boolean = true
) => {
  const url = `${getBaseUrl()}/web/v3/account/employee-cookie`;
  const headers = getHeaders({ auth: uniqueToken });
  const b: ApiInvokerBuilder = Object.assign({}, defaults, {
    setBody: (v: StringIndexer) => {
      b.data = v;
      // console.log("header=>", JSON.stringify(headers));
      // console.log("body=>", b.data);
      return b;
    },
    exec: () =>
      fetch(url, {
        method: "POST",
        headers: headers,
        body: isUndefined(b.data) ? undefined : JSON.stringify(b.data),
      })
        .then((res) => res.json())
        .then((ret) => {
          if (
            !isUndefined(ret.error_code) &&
            ret.error_code === "SSO_TOKEN_NOT_FOUND"
          ) {
            console.log("uniqueToken expired 需要刷新");
            throw new UniqueTokenExpiredError("SSO_TOKEN_NOT_FOUND");
          }
          return ret;
        })
        .then((ret) => {
          console.log("genWebToken ret=>", ret);
          if (!isUndefined(ret.error_message)) {
            throw Error(ret.error_message);
          }
          return ret;
        })
        .then((ret) => toString(ret.id)),
  });

  return b;
};

export interface WebposProdType {
  id: number;
  name: string;
  subs?: WebposProdType[];
}

export const getWebposProdTypeList = (webposToken: string) => {
  const url = `${getBaseUrl()}/web/v3/prod-type`;
  // console.log('webposToken=>', webposToken)
  const headers = getHeaders({ token: webposToken });
  const b: ApiInvokerBuilder = Object.assign({}, defaults, {
    setFilter: (v: ApiInvokerFilter) => {
      b.filter = v;
      return b;
    },
    exec: (): Promise<WebposProdType> =>
      fetch(url, { method: "GET", headers: headers })
        .then((res) => res.json())
        .then((ret) => {
          // console.log("getWebposProdTypeList ret=>", ret);
          if (!isUndefined(ret.error_message)) {
            throw Error(ret.error_message);
          }
          return ret;
        })
        .then((ret) => {
          let prod_types = ret.prod_types;
          if (!isUndefined(b.filter)) {
            prod_types = (prod_types as StringIndexer[]).filter(b.filter);
          }
          return prod_types;
        }),
  });
  return b;
};

export const createWebposProdType = (webposToken: string) => {
  const url = `${getBaseUrl()}/web/v3/prod-type`;
  const headers = getHeaders({ token: webposToken });
  const b: ApiInvokerBuilder = Object.assign({}, defaults, {
    setBody: (v: StringIndexer) => {
      b.data = v;
      return b;
    },
    exec: () =>
      fetch(url, {
        method: "POST",
        headers: headers,
        body: isUndefined(b.data) ? undefined : JSON.stringify(b.data),
      })
        .then((res) => res.json())
        .then((ret) => {
          console.log("createWebposProdType ret=>", ret);
          if (!isUndefined(ret.error_message)) {
            throw Error(ret.error_message);
          }
          return ret;
        })
        .then((ret) => toString(ret.id)),
  });

  return b;
};

export const getRemotePrdTypeIdSafely = async (
  webposToken: string,
  brandName: string
) => {
  const parentRemoteRrdTyppeNameForBlinggroup = `(星团专用)${brandName}`;
  const sonRemoteRrdTyppeNameForBlinggroup = `未分类(星团专用)${brandName}`;

  let isParentOk = false,
    isSonOk = false;
  let parentId: string = "",
    sonId: string = "";
  const filter = (v: StringIndexer) => {
    if (v["name"] == parentRemoteRrdTyppeNameForBlinggroup) {
      parentId = v["id"];
      isParentOk = true;
      // console.log(`1 parentId=>${parentId}, sonId=>${sonId}`);
      let subs: StringIndexer[] = v["subs"];
      let sub = subs.find(
        (it) => it["name"] == sonRemoteRrdTyppeNameForBlinggroup
      );
      if (!isUndefined(sub)) {
        sonId = sub["id"];
        isSonOk = true;
        // console.log(`2 parentId=>${parentId}, sonId=>${sonId}`);
        return true;
      }
    }
    // console.log(`3 parentId=>${parentId}, sonId=>${sonId}`);
    return false;
  };

  await getWebposProdTypeList(webposToken).setFilter(filter).exec();

  console.log(`4 parentId=>${parentId}, sonId=>${sonId}`);
  if (!isParentOk) {
    parentId = await createWebposProdType(webposToken)
      .setBody({
        p_store_id: 0,
        parent_id: 0,
        name: parentRemoteRrdTyppeNameForBlinggroup,
      })
      .exec();
  }
  // console.log(`5 parentId=>${parentId}, sonId=>${sonId}`);
  if (!isSonOk) {
    sonId = await createWebposProdType(webposToken)
      .setBody({
        p_store_id: 0,
        parent_id: Number(parentId),
        name: sonRemoteRrdTyppeNameForBlinggroup,
      })
      .exec();
  }
  // console.log(`6 parentId=>${parentId}, sonId=>${sonId}`);
  return sonId;
};

export interface WebposPropertyGroup {
  id: number;
  name: string;
  properties: string[];
}

export const getWebposPropertyGroupList = (webposToken: string) => {
  const url = `${getBaseUrl()}/web/v3/property-group?p_store_id=0`;
  // console.log('webposToken=>', webposToken)
  const headers = getHeaders({ token: webposToken });
  const b: ApiInvokerBuilder = Object.assign({}, defaults, {
    setFilter: (v: ApiInvokerFilter) => {
      b.filter = v;
      return b;
    },
    exec: (): Promise<WebposPropertyGroup> =>
      fetch(url, { method: "GET", headers: headers })
        .then((res) => res.json())
        .then((ret) => {
          // console.log("getWebposPropertyGroupList ret=>", ret);
          if (!isUndefined(ret.error_message)) {
            throw Error(ret.error_message);
          }
          return ret;
        })
        .then((ret) => {
          let propertyGroups = ret.groups;
          if (!isUndefined(b.filter)) {
            propertyGroups = (propertyGroups as WebposPropertyGroup[]).filter(
              b.filter
            );
          }
          return propertyGroups;
        }),
  });
  return b;
};

export const saveWebposPropertyGroup = (webposToken: string) => {
  const url = `${getBaseUrl()}/web/v3/property-group`;
  const headers = getHeaders({ token: webposToken });
  const b: ApiInvokerBuilder = Object.assign({}, defaults, {
    setBody: (v: StringIndexer) => {
      b.data = Object.assign({ p_store_id: 0, type_ids: [] }, v);
      console.log("body=>", b.data);
      return b;
    },
    exec: () =>
      fetch(url, {
        method: isUndefined(b.data!["id"]) ? "POST" : "PUT",
        headers: headers,
        body: isUndefined(b.data) ? undefined : JSON.stringify(b.data),
      })
        .then((res) => res.json())
        .then((ret) => {
          console.log("saveWebposPropertyGroup ret=>", ret);
          if (!isUndefined(ret.error_message)) {
            throw Error(ret.error_message);
          }
          return ret;
        })
        .then((ret) =>
          toString(isUndefined(b.data!["id"]) ? ret.id : b.data!["id"])
        ),
  });

  return b;
};

export const getRemotePropertyGroupIdSafely = async (
  webposToken: string,
  propertyAttributeDefs: ProductAttributeDefExt[]
) => {
  const webposPropertyGroups: WebposPropertyGroup[] = await getWebposPropertyGroupList(
    webposToken
  ).exec();
  // console.log('webposPropertyGroups=>', JSON.stringify(webposPropertyGroups))
  const existMapPropertyGroups = reduceToObject(
    webposPropertyGroups,
    (o) => `${o.id}`,
    (o) => o
  );
  const existsMapPropertyGroupNameToId = reduceToObject(
    webposPropertyGroups,
    (o) => o.name,
    (o) => `${o.id}`
  );

  const builder = saveWebposPropertyGroup(webposToken);
  for (const pad of propertyAttributeDefs) {
    // 判断要不要调用
    let needSave = false;
    let saveBody: StringIndexer = { name: pad.key, properties: pad.values };
    let remotePrdGroupIdOfCurrent = pad.remotePK;
    if (isUndefined(remotePrdGroupIdOfCurrent)) {
      remotePrdGroupIdOfCurrent = existsMapPropertyGroupNameToId[pad.key];
    }
    if (remotePrdGroupIdOfCurrent) {
      console.log(`[${pad.key}]商品中心已存在属性组,需要进一步判断`);
      const { name, properties } = existMapPropertyGroups[
        remotePrdGroupIdOfCurrent
      ];
      const needCreateGroup = pad.key !== name;
      if (needCreateGroup) {
        console.log(`[${pad.key}]属性名变更,创建新的属性组`);
        needSave = true;
      } else {
        // 判断属性组是否有变动的属性项目, 目前不比较顺序
        const needUpdate = pad.values.some((v) => !properties.includes(v));
        if (needUpdate) {
          console.log(`[${pad.key}]需要修改商品中心属性组`);
          needSave = true;
          saveBody.id = remotePrdGroupIdOfCurrent;
          saveBody.properties = union(properties, saveBody.properties);
        } else {
          console.log(`[${pad.key}]无需修改商品中心属性组`);
        }
      }
    } else {
      console.log(`[${pad.key}]需要新增商品中心属性组`);
      needSave = true;
    }

    if (needSave) {
      remotePrdGroupIdOfCurrent = await builder.setBody(saveBody).exec();
    }
    pad.remotePK = remotePrdGroupIdOfCurrent;

    pad.remoteData = {
      id: Number(pad.remotePK),
      name: pad.key,
      properties: pad.values,
    };
  }

  return propertyAttributeDefs;
};

export const saveWebposProd = (webposToken: string) => {
  const url = `${getBaseUrl()}/web/v3/prod-info`;
  const headers = getHeaders({ token: webposToken });
  const b: ApiInvokerBuilder = Object.assign({}, defaults, {
    setBody: (v: StringIndexer) => {
      b.data = Object.assign(
        {
          p_store_id: 0,
          prod_nature: 2,
          phonetic: "BG",
          standard_weight: 0,
          unit: "份",
          use_standard: 1,
          use_weight: 0,
          ext_code: "",
        },
        v
      );
      // console.log("body=>", b.data);
      return b;
    },
    exec: () =>
      fetch(url, {
        method: isUndefined(b.data!["id"]) ? "POST" : "PUT",
        headers: headers,
        body: JSON.stringify(b.data),
      })
        .then((res) => res.json())
        .then((ret) => {
          console.log("saveWebposProd ret=>", JSON.stringify(ret));
          if (!isUndefined(ret.error_message)) {
            throw Error(ret.error_message);
          }
          return ret;
        }),
    // .then((ret) => ret),
  });

  return b;
};

interface WebposPrdSKU {
  id?: number;
  property_pair: {
    id: number; // 属性组id 颜色id
    name: string; // 属性值 红色
  }[];
  price: number;
  weight: number;
  ext_code: string;
}
interface WebposPrdInfo {
  id?: number;
  name: string;
  prod_type_id: number;
  standard_price: number;
  skus: WebposPrdSKU[];
  title_pics: string[];
  detail_description: string;
  detail_pics: string[];
}

export interface AddWebposPrdParams {
  name: string;
  price?: number;
  pics: string[];
  intro?: string;
  introPics: string[];
  productGroupId?: string;
  productSpecs: ProductSpec[];
  remotePrdTypeId: string;
}

export const addWebposPrd = (
  webposToken: string,
  brandId: string,
  productAttributeDefs: ProductAttributeDefExt[]
) => async ({
  name,
  price,
  pics,
  intro,
  introPics,
  productGroupId,
  productSpecs,
  remotePrdTypeId,
}: AddWebposPrdParams): Promise<CreateProductParams> => {
  const hasSpecs = productSpecs.length > 0;

  const productAttributeDefsMapNameToId = reduceToObject(
    productAttributeDefs,
    (o) => o.key,
    (o) => `${o.remotePK}`
  );

  let standard_price: number;
  if (hasSpecs) {
    standard_price = _min<ProductSpec>(productSpecs, (a, b) => {
      return a.price - b.price;
    })!.price;
  } else {
    standard_price = price!;
  }

  const skus = productSpecs.map((it) => {
    // it.remotePK
    const property_pair = it.productAttributes.map((pa) => ({
      id: Number(productAttributeDefsMapNameToId[pa.key]),
      name: pa.value,
    }));
    const sku: WebposPrdSKU = {
      id: isUndefined(it.remotePK) ? undefined : Number(it.remotePK),
      property_pair,
      price: toFixed(it.price / 100, 2),
      weight: 0,
      ext_code: "",
    };
    return sku;
  });

  let prdInfo = {
    use_standard: hasSpecs ? 1 : 0,
    name,
    prod_type_id: Number(remotePrdTypeId),
    title_pics: pics,
    detail_description: intro || "",
    detail_pics: introPics,
    standard_price,
    skus,
  };

  const createdPrd: WebposPrdInfo = await saveWebposProd(webposToken)
    .setBody(prdInfo)
    .exec();
  // console.log("-------createdPrd=>", createdPrd);
  const createdSkusMap = reduceToObject(
    createdPrd.skus,
    (o: WebposPrdSKU) => {
      o.property_pair.sort();
      const mapKey = o.property_pair.reduce((agg, o) => `${agg}-${o}`, "");
      console.log("remote:", mapKey);
      return mapKey;
    },
    (o: WebposPrdSKU) => ({
      id: o.id!,
      price: o.price,
      property_pair: o.property_pair,
    })
  );

  console.log("createdSkusMap=>", JSON.stringify(createdSkusMap));

  // 需要将规格id更新回来
  productSpecs.forEach((spec) => {
    const sortingProductAttributes = [...spec.productAttributes];
    sortingProductAttributes.sort((a, b) => {
      if (a.value < b.value) {
        return -1;
      }
      if (a.value > b.value) {
        return 1;
      }
      return 0;
    });
    let mapKey = sortingProductAttributes.reduce(
      (agg, o) => `${agg}-${o.value}`,
      ""
    );
    console.log("local:", mapKey);
    spec.remotePK = `${createdSkusMap[mapKey].id}`;
    spec.remoteData = createdSkusMap[mapKey];
  });

  console.log("productSpecs=>", JSON.stringify(productSpecs));

  // "id", "name", "title_pics", "detail_description", "detail_pics", "standard_price", "prod_type_id"

  // const remoteData = {
  //   id: createdPrd.id!,
  //   name: createdPrd.name,
  //   title_pics: createdPrd.title_pics,
  //   detail_description: createdPrd.detail_description,
  //   detail_pics: createdPrd.detail_pics,
  //   standard_price: createdPrd.standard_price,
  //   prod_type_id: createdPrd.prod_type_id,
  // }

  const p: CreateProductParams = {
    // origin: DataSource.LOCAL,
    brandId,
    name,
    price,
    pics,
    intro,
    introPics,
    productGroupId,
    // remotePrdTypeId,
    // remotePK: `${createdPrd.id}`,
    productAttributeDefs,
    productSpecs,
    // remoteData: createdPrd,
  };
  return p;
};

export interface EditWebposPrdParams {
  remotePK: string;
  brandId: string;
  name: string;
  price?: number;
  pics: string[];
  intro?: string;
  introPics: string[];
  productGroupId?: string;
  productSpecs: ProductSpec[];
  remotePrdTypeId: string;
}

export const editWebposPrd = (
  webposToken: string,
  productId: string,
  productAttributeDefs: ProductAttributeDefExt[]
) => async ({
  remotePK,
  brandId,
  name,
  price,
  pics,
  intro,
  introPics,
  productGroupId,
  productSpecs,
  remotePrdTypeId,
}: EditWebposPrdParams): Promise<UpdateProductParams> => {
  const hasSpecs = productSpecs.length > 0;

  const productAttributeDefsMapNameToId = reduceToObject(
    productAttributeDefs,
    (o) => o.key,
    (o) => `${o.remotePK}`
  );

  let standard_price: number;
  if (hasSpecs) {
    standard_price = _min<ProductSpec>(productSpecs, (a, b) => {
      return a.price - b.price;
    })!.price;
  } else {
    standard_price = price!;
  }

  const skus = productSpecs.map((it) => {
    // it.remotePK
    const property_pair = it.productAttributes.map((pa) => ({
      id: Number(productAttributeDefsMapNameToId[pa.key]),
      name: pa.value,
    }));
    const sku: WebposPrdSKU = {
      id: isUndefined(it.remotePK) ? undefined : Number(it.remotePK),
      property_pair,
      price: toFixed(it.price / 100, 2),
      weight: 0,
      ext_code: "",
    };
    return sku;
  });

  let prdInfo = {
    id: Number(remotePK),
    use_standard: hasSpecs ? 1 : 0,
    name,
    prod_type_id: Number(remotePrdTypeId),
    title_pics: pics,
    detail_description: intro || "",
    detail_pics: introPics,
    standard_price,
    skus,
  };

  const editedPrd: WebposPrdInfo = await saveWebposProd(webposToken)
    .setBody(prdInfo)
    .exec();
  // console.log("-------editedPrd=>", editedPrd);
  const skusMap = reduceToObject(
    editedPrd.skus,
    (o: WebposPrdSKU) => {
      o.property_pair.sort();
      const mapKey = o.property_pair.reduce((agg, o) => `${agg}-${o}`, "");
      console.log("remote:", mapKey);
      return mapKey;
    },
    (o: WebposPrdSKU) => ({
      id: o.id!,
      price: o.price,
      property_pair: o.property_pair,
    })
  );

  console.log("editedSkusMap=>", JSON.stringify(skusMap));

  // 需要将规格id更新回来
  productSpecs.forEach((spec) => {
    const sortingProductAttributes = [...spec.productAttributes];
    sortingProductAttributes.sort((a, b) => {
      if (a.value < b.value) {
        return -1;
      }
      if (a.value > b.value) {
        return 1;
      }
      return 0;
    });
    let mapKey = sortingProductAttributes.reduce(
      (agg, o) => `${agg}-${o.value}`,
      ""
    );
    console.log("local:", mapKey);
    spec.remotePK = `${skusMap[mapKey].id}`;
    spec.remoteData = skusMap[mapKey];
  });

  console.log("productSpecs=>", JSON.stringify(productSpecs));

  const p: UpdateProductParams = {
    productId,
    brandId,
    name,
    price,
    pics,
    intro,
    introPics,
    productGroupId,
    // remotePrdTypeId,
    productAttributeDefs,
    productSpecs,
    // remoteData: editedPrd,
  };
  return p;
};
